/*
 * Calculator Default ButtonHandler
 *
 * Developed for "Rethinking CS101", a project of Lynn Andrea Stein's AP Group.
 * For more information, see <a href="http://www.ai.mit.edu/projects/cs101">the
 * CS101 homepage</a> or email <las@ai.mit.edu>.
 *
 * Copyright (C) 1996 Massachusetts Institute of Technology.
 * Please do not redistribute without obtaining permission.
 */
package ratiocalc;
import cs101.util.*;

/**
 * This class provides the smarts for a basic four-function 
 * calculator.  It repeatedly calls its Calculator(GUI)'s getButton()
 * to consume the buttonIDs that the Calculator object produces. <p>
 *
 * <P>Copyright (c) 1998 Massachusetts Institute of Technology
 *
 * @author  Todd C. Parnell, tparnell@ai.mit.edu
 * @author  Emil Sit, sit@mit.edu
 * @author  Lynn Andrea Stein, las@ai.mit.edu
 * @version $Id: ButtonHandler.java,v 1.5 1998/07/24 16:37:13 tparnell Exp $
 *
 * @see Calculator
 * @see cs101.util.IntBuffer
 */

public class ButtonHandler implements Runnable {

  protected Calculator gui;
  protected Thread spirit;
  /**
   * Flag to tell whether the Thread running this should be stopped.
   */
  private boolean stopped = false;
    
  // State variables
  // All are initialized by resetCalc();
  // Note:  currArg is always still being created 
  //        and so is stored in the gui's text 
  protected boolean currArgIsInt, numStartsNewArg, opPending, gotOpSinceEquals;
  protected Ratio prevArg, pendingArg;
  protected int currOp, pendingOp;

  /**
   * Build a ButtonHandler object from a Calculator gui passed in as
   * an argument.
   */
  protected ButtonHandler( Calculator gui ) {
    this.gui = gui;
    this.resetCalc();
    this.spirit = new Thread(this);
    this.spirit.start();
  }


  /**
   * Provides the interactive control loop of this animate object.
   * @see java.lang.Runnable
   */
  public void run () {

  HANDLEBUTTON:
    while (!this.stopped) {
      int buttonID = this.gui.getButton();

      try {

	this.printState("handling: "+
			this.gui.getButtonLabel(buttonID));
 
	// Deal with number buttons -- add to display text
	if ( buttonID < 10 && buttonID >= 0) {
	  this.handleNumKey( buttonID );
	  continue HANDLEBUTTON;
	}

	// deal with everything else
	switch ( buttonID ) {
	case Calculator.DEC:
		 Ratio r = makeRatio(this.gui.getText());
		 this.gui.setText(String.valueOf(r.value()));
		 break;
	case Calculator.OP_MUL:
	case Calculator.OP_DIV:
	case Calculator.OP_ADD:
	case Calculator.OP_SUB:
	  this.handleOp( buttonID );
	  break;
	case Calculator.EQUALS:
	  this.handleEquals();
	  break;
	case Calculator.CLEAR:
	  this.resetCalc();
	  break;
	// added the the button for ratio: 
	case Calculator.RATIO:
		// check if there's already
		// a / there: 
		
		if (this.gui.getText().indexOf('/') == -1) {
					
		this.gui.setText(this.gui.getText()+"/");
		
		}
		break;
	case Calculator.BLANK:
		// there can only be one blank
		if (this.gui.getText().indexOf(' ')!=-1 
			|| this.gui.getText().charAt(0)=='0') 
			break;
		else {
			this.gui.setText(this.gui.getText()+" ");
			break;
			}

	case Calculator.BACK:
		if (this.gui.getText().length()!=0){
			this.gui.setText(this.gui.getText().substring
					(0, this.gui.getText().length()-1));
		}
			
	default:
	  this.printState("ERROR:  unmatched buttonID.");
	}
      } finally {
	this.numStartsNewArg = !(( buttonID < 10 ) 
//				 || ( buttonID == Calculator.DOT )
				 
				 // if ratio, blank or back is pressed
				 // use  it on the screen
				 || ( buttonID == Calculator.RATIO )
				 || ( buttonID == Calculator.BLANK )
				 || ( buttonID == Calculator.BACK )
				 );
	
      }
    }
  }

  protected void printState(String s) {
    /*
     *	  System.out.println (s
     *			  +"\nnumStartsNewArg: "+numStartsNewArg
     *			  +"\ngotOpSinceEquals: "+gotOpSinceEquals
     *			  +"\nprevArg: "+prevArg
     *			  +"\ncurrOp: "+((currOp == Calculator.NO_OP)?
     *					 "NO_OP":
     *					 this.gui.getButtonLabel(currOp))
     *			  +"\nopPending: "+opPending
     *			  +"\npendingOp: "+((pendingOp == Calculator.NO_OP)?
     *					    "NO_OP":
     *					    this.gui.getButtonLabel(pendingOp))
     *			  +"\npendingArg: "+pendingArg
     *			  +"\n");
     */
  }

  protected void handleNumKey( int num ) {
    if ( this.numStartsNewArg ) {
      this.clearScreen();
      this.resetDecimal();
    }
    this.gui.setText( this.gui.getText() + this.gui.getButtonLabel(num) );
  }

  /*protected void handleDecimal () {
    if ( this.numStartsNewArg ) {
      this.handleNumKey( 0 );
    }
    if ( this.currArgIsInt ) {
      this.currArgIsInt = false;
      this.gui.setText( this.gui.getText() + ".");
    }
  }
  */
	
  protected void handleOp( int op ) {
    if ( this.gotOpSinceEquals ) {
      if ( this.numStartsNewArg ) {  // +-
	// System.out.println("handling +-:  change op & return.\n");
	this.currOp = op;
	return;
      } else {
	boolean newOpIsMajor =( op == Calculator.OP_MUL )
	  || ( op == Calculator.OP_DIV );
	boolean currOpIsMinor = ( this.currOp == Calculator.OP_ADD )
	  || ( this.currOp == Calculator.OP_SUB );
	if ( ! this.opPending  && newOpIsMajor && currOpIsMinor ) {
	  // System.out.println("newOpMajor, currOpMinor,"
	  // + "set up pending op.\n");
	  this.opPending = true;
	  this.pendingOp = this.currOp;
	 
	  
	  this.pendingArg = this.prevArg;
	} else {
	  if ( this.opPending && newOpIsMajor && (! currOpIsMinor) ) {
	    this.opPending = false;
	    // System.out.println("handling =, faking it.\n");
	    this.handleEquals();
	    this.opPending = true;
	  } else {
	    // System.out.println("handling =\n");
	    this.handleEquals();
	  }
	}
      }
      this.printState("After pending, handleEquals\n");
    } else {
      // System.out.println("first op since =\n");
    }
    this.currOp = op;
    this.prevArg = makeRatio( this.gui.getText() );
    this.gotOpSinceEquals = true;
  }

  protected Ratio makeRatio (String s) {
	 
	  int nom, denom;
	  
	  if (s.indexOf('/')==-1 && s.indexOf(' ')==-1){
		  nom = Integer.parseInt(s);
		  denom = 1;
	  }
	  
	  else if (s.indexOf(' ')!=-1) {
		  denom = Integer.parseInt(s.substring(s.indexOf('/')+1)); 
		  nom = (Integer.parseInt(s.substring(0, s.indexOf(' ')))*denom)
		  		+ Integer.parseInt(s.substring(s.indexOf(' ')+1, s.indexOf('/')));
	  }
	  else {
	  nom = Integer.parseInt(s.substring(0, s.indexOf('/')));
	  denom = Integer.parseInt(s.substring(s.indexOf('/')+1));
	  
	  }
	  // initialize default ratio for catch case
	  
	  Ratio r= new Ratio(-1);
	  
	try {
		r = new Ratio (nom, denom);
	} catch (DivisionByZeroException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
		
	}
		  
		  return r;
	  
  }
  
  protected void handleEquals () {

    // note:  could be called by = or op!
    try {
      Ratio bufval = makeRatio( this.gui.getText() );
      Ratio ans;

      if ( this.gotOpSinceEquals ) { // 3+4= or 3+= or called by op
	// System.out.println("Doing = w/op:  either 3+4= or 3+=\n");
	ans = this.doOperation ( this.currOp, this.prevArg, bufval );
	if ( this.opPending ) {
	  // System.out.println("handling pending op.\n");
	  ans = this.doPendingOp( ans );
	}
	// set up for repeated ops....
	this.prevArg = bufval;  // op will be currOp
      } else { // 3= or ==
	if ( this.numStartsNewArg ) {      // ==
	  // System.out.println("Doing ==\n");
	  ans = this.doOperation ( this.currOp, 
				   bufval,
				   this.prevArg );
	} else { // 3=
	  // System.out.println("Doing num=.\n");
	  // do nothing
	  this.prevArg = bufval;
	  this.currOp = Calculator.NO_OP;
	  return;
	}

      }
     // this.currArgIsInt = ( Math.floor( ans ) == ans );

      this.clearScreen();
      this.gui.setText(ans.print() );

    } finally {
      this.gotOpSinceEquals = false;
    }
  }


  Ratio doPendingOp( Ratio val ) {
    Ratio ans = this.doOperation ( this.pendingOp, 
				    this.pendingArg,
				    val );
    this.pendingOp = Calculator.NO_OP;
    this.pendingArg = null;
    this.opPending = false;
    return ans;
  }

  protected Ratio doOperation ( int op, Ratio prevArg, Ratio currArg ) {
//     System.out.println("doOp:  Calculating: "+String.valueOf(prevArg)+
// 		       ((op == Calculator.NO_OP)?
// 			" (NO_OP)":
// 			(this.gui.getButtonLabel(op)+
// 			 String.valueOf(currArg)))+"\n");
	
	 switch ( op ) {
    case Calculator.OP_MUL:
    	prevArg.multWith(currArg);
    	// return the changed ratio
    	// to avoid having to throw 
    	// excectop div by zero, 
    	// we don't make a new ratio
    	// but reurn the changed prevArg
    	return prevArg;
    case Calculator.OP_DIV:
    	try {
				prevArg.divBy(currArg);
				return (prevArg);
			} catch (DivisionByZeroException e) {
				// if div by zero occurs, 
				e.printStackTrace();
				this.gui.setText("Division By Zero, na na!");
			}
    	
    case Calculator.OP_SUB:
    	prevArg.subFrom(currArg);
    	return (prevArg);
    case Calculator.OP_ADD:
    	prevArg.addTo(currArg);
    	return (prevArg);
    case Calculator.NO_OP:
      // fall through....
    default:
      return ( currArg );
    }
  }

  protected void resetDecimal () {
    this.currArgIsInt = true;
  }

  protected void clearScreen() {
    this.gui.setText( "" );
  }

  protected void resetCalc() {
    this.clearScreen();
    this.gui.setText( String.valueOf( 0 ) );
    this.prevArg   = this.pendingArg = null;
    this.currArgIsInt = true;
    this.numStartsNewArg = true;
    this.opPending = false;
    this.gotOpSinceEquals = false;
    this.currOp = this.pendingOp = Calculator.NO_OP;
  }

}


/* Comments:
 *
 * History:
 *     $Log: ButtonHandler.java,v $
 *     Revision 1.5  1998/07/24 16:37:13  tparnell
 *     Placate new javadoc behavior
 *
 *     Revision 1.4  1998/07/23 14:28:26  tparnell
 *     made class public
 *
 *     Revision 1.3  1998/07/06 20:22:56  tparnell
 *     changed from while (true)... to while (!this.stopped)... in support of
 *     JDK1.2 deprecation of Thread.stop()
 *
 *     Revision 1.2  1998/06/05 05:19:26  craigh
 *     added getButtonLabel() to Calculator interface.  Implemented the
 *     method in CalculatorGUI, and made use of it in ButtonHandler.
 *
 *     Revision 1.1  1998/02/26 17:25:43  tparnell
 *     Reconstruction from hard drive failure.  Everything appears intact.
 *
 *     Revision 1.3  1997/10/05 21:11:18  shong
 *     Updated for fall97, to Java 1.1
 *     changed GUI, using 1.1 Event Model
 *
 *     Revision 1.2  1997/07/16 14:15:19  tparnell
 *     *** empty log message ***
 *
 *     Revision 1.2  1996/10/04 16:20:17  las
 *     Transformed Calculator into an application and made it a package.  See
 *     STAFF_SETUP for which files are public.  To run, use Calculator.Main.
 *
 *     Specifics:
 *         Added Main.java, which starts the calculator program (both
 *     CalculatorGUI and ButtonHandler);
 *         Made Calculator an interface;
 *         Moved GUI implementation (previously in Calculator) to
 *     CalculatorGUI.
 *         Added clear button, which looks pretty gross right now.  (It can
 *     be deleted in a single line, though.)
 *
 *
 */

